package fge;

import com.mxgraph.model.mxCell;
import com.mxgraph.model.mxGeometry;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxUtils;
import java.util.ArrayList;
import java.util.List;

public class Fvertex extends mxCell {

	private String name = "";

	/**
	 * 0 means 0 degrees.
	 * 1 means 90 degrees.
	 * 2 means 180 degress.
	 * 3 means 270 degrees.
	 */
	private int curr_rotation = 0;
	
	private String my_id;
	
	public Fvertex() {
		super();
		this.setVertex(true);		// This is a vertex, not an edge

		/**
		 * JGraph does not automatically update the Id when creating new cells.
		 * So we roll our own. An incremental counter for uniqueness is sufficient.
		 */
		this.my_id = Integer.toString(GlobalCounter.getCount());
	}

	@Override
	public String getId() {
		return this.my_id;
	}

	@Override
	public void setId(String id) {
		this.my_id = id;
	}

	
	
	public List<Fvertex> getChildFvertices() {
		List<Fvertex> ret = new ArrayList<Fvertex>();

		if (this.children == null) {
			return ret;	// JGraph will first allocate space for children, when an mxCell has some
		}
		for (Object o : this.children) {
			if (o instanceof Fvertex) {
				ret.add((Fvertex) o);
			}
		}

		return ret;
	}
	
	/**
	 * getChildEdges() will return the edges having this Fvertex as their parent.
	 * This is unlike the this.edges-member of all mxCells, which is the edges connecting to/from the mxCell.
	 * @return The edges having 'this' as parent.
	 */
	public List<mxCell> getChildEdges() {
		List<mxCell> ret = new ArrayList<mxCell>();

		if (this.children == null) {
			return ret;	// JGraph will first allocate space for children, when an mxCell has some
		}
		for (Object o : this.children) {
			if (o instanceof mxCell) {
				if (((mxCell)o).isEdge()) {
					ret.add((mxCell) o);
				}
			}
		}

		return ret;
	}
	
	/**
	 * Sets a value for the given style-property.
	 * If the property was set before, it will be overridden; otherwise it will be created.
	 * @param key		The style-property to change
	 * @param value		The new value of the style-property
	 * @return			The new entire style-string
	 */
	public String changeStyle(String key, String value) {
		String old_style = this.getStyle();
		String new_style = mxUtils.setStyle(old_style, key, value);
		this.setStyle(new_style);
		return new_style;
	}
	
	public String changeStyle(String key, int value) {
		return this.changeStyle(key, ""+value);
	}

	public void setRotation(int new_rotation) {
		new_rotation = new_rotation % 4;	// Ensure [0..3] interval

		mxGeometry parent_old = (mxGeometry)this.getGeometry().clone();
		int diff = new_rotation - curr_rotation;
		int absdiff = java.lang.Math.abs(curr_rotation - new_rotation);
		if (absdiff % 2 == 1) {
			double h = this.getHeight();
			double w = this.getWidth();
			this.setHeight(w);
			this.setWidth(h);
		}
		mxGeometry parent_new = this.getGeometry();
		
		List<Fvertex> fchildren = this.getChildFvertices();
		for (Fvertex f : fchildren) {
			int rot = f.getRotation() + diff;
			f.update_xy(rot, parent_old, parent_new);
			f.setRotation(rot);
		}

		this.curr_rotation = new_rotation;
	}
	
	private void update_xy(int new_rotation, mxGeometry parent_old, mxGeometry parent_new) 
	{
		new_rotation = new_rotation % 4;	// Ensure [0..3] interval
		int absdiff = java.lang.Math.abs(curr_rotation - new_rotation);

		boolean before = (curr_rotation == 0 || curr_rotation == 1);
		boolean after = (new_rotation == 0 || new_rotation == 1);

		mxGeometry geo = this.getGeometry();
		double x = geo.getX();
		double y = geo.getY();
		double w = geo.getWidth();
		double h = geo.getHeight();

		if (geo.isRelative() == false) {
			// Make it scaled (already abs-relative inside)
			x = (x + w/2) / parent_old.getWidth();
			y = (y + h/2) / parent_old.getHeight();
		}

		if (before != after) {
			x = 1.0f - x;
			y = 1.0f - y;
		}
		
		if (new_rotation == 1 || new_rotation == 3) {	// Mirror back
			y = 1.0f - y;			
		}
		if (new_rotation == 0 || new_rotation == 2) {	// Mirror back
			x = 1.0f - x;			
		}
		
		if (absdiff % 2 == 1) {
			double t;
			t=x; x=y; y=t;	// swap x and y
			t=w; w=h; h=t;	// swap w and h
		}

		if (geo.isRelative() == false) {
			// Calc absolute position
			x = x*parent_new.getWidth() - w/2;
			y = y*parent_new.getHeight() - h/2;
		}

		geo.setX(x);
		geo.setY(y);
		this.setGeometry(geo);
	}
	
	public int getRotation() {
		return this.curr_rotation;
	}
	
	
	@Override
	public String toString() {
		return this.name;
	}
	
	/**
	 * getName() is the one that is shown to the user in the GUI (implicitly via .toString())
	 * getUniqueName() is intended for code-generation.
	 * @return A unique name for this vertex
	 */
	public String getUniqueName() {
		return this.toString() + "_" + this.getId();
	}

	/**
	 * This overrides mxCell's getValue(). Normal mxCells can have a user object as their value.
	 * In our case, we don't want to invoke getValue() many many times, so we let Fcomponent itself be the value.
	 * @return
	 */
	@Override
	public Object getValue() {
		return this;	// JGraph obtains labels by getValue().toString()
	}

	/**
	 * It makes no sense to call setValue as the value of a Fcomponent is itself
	 */
	@Override
	public void setValue(Object value) {
		// Intentionally left empty
	}

	public String getName() {
		return this.name;
	}

	public final void setName(String name) {
		String allowed_name = "";
		for (char c : name.toCharArray()) {
			if (c == '_' || ('0'<=c && c<='9') || ('a'<=c && c<='z') || ('A'<=c && c<='Z')) {
				allowed_name += c;
			}
		}
		this.name = allowed_name;
	}

	public double getWidth() {
		return this.getGeometry().getWidth();
	}

	public void setWidth(double width) {
		mxGeometry geo = this.getGeometry();
		geo.setWidth(width);
		this.setGeometry(geo);
	}

	public double getHeight() {
		return this.getGeometry().getHeight();
	}

	public void setHeight(double height) {
		mxGeometry geo = this.getGeometry();
		geo.setHeight(height);
		this.setGeometry(geo);
	}
}
